package org.zebra.mybatis.plus.config;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;
import org.dromara.streamquery.stream.plugin.mybatisplus.engine.annotation.EnableMybatisPlusPlugin;
import org.dromara.streamquery.stream.plugin.mybatisplus.engine.configuration.StreamScannerConfigurer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.zebra.common.core.BaseResponseBodyAdviceChain;
import org.zebra.mybatis.plus.core.DynamicServicePlusImpl;
import org.zebra.mybatis.plus.core.MybatisPlusBaseResponseBodyAdviceChain;
import org.zebra.mybatis.plus.druid.Slf4jLogInfoLevelFilter;
import org.zebra.mybatis.plus.ext.DynamicMybatisPlusInterceptor;
import org.zebra.mybatis.plus.ext.MetaObjectHandlerPlus;
import org.zebra.mybatis.plus.ext.audit.DataAuditFieldTranslation;
import org.zebra.mybatis.plus.ext.audit.DataAuditInterceptor;
import org.zebra.mybatis.plus.ext.fill.AutoFillMetaObjectHandler;
import org.zebra.mybatis.plus.ext.redundancy.RedundancyFieldEventRegister;

import java.util.HashSet;
import java.util.Properties;

/**
 * AutoConfiguration before 需要在MybatisPlusJoinAutoConfiguration之前注入，
 * 不然StreamPluginAutoConfiguration DefaultSqlInjector 不会生效
 *
 * @author zhanghongbin
 */
@AutoConfiguration(before = MybatisPlusJoinAutoConfiguration.class)
@Import({DynamicMybatisPlusInterceptor.class, RedundancyFieldEventRegister.class})
@EnableMybatisPlusPlugin
public class MybatisPlusConfig implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @AutoConfiguration
    static class MybatisPlusBaseResponseBodyAdviceChainConfig {
        public MybatisPlusBaseResponseBodyAdviceChainConfig(
                @Autowired(required = false) BaseResponseBodyAdviceChain baseResponseBodyAdviceChain) {
            if (baseResponseBodyAdviceChain != null) {
                BaseResponseBodyAdviceChain temp = baseResponseBodyAdviceChain.getBaseResponseBodyAdviceChain();
                BaseResponseBodyAdviceChain preTemp = baseResponseBodyAdviceChain;
                while (true) {
                    if (temp == null) {
                        preTemp.setBaseResponseBodyAdvice(new MybatisPlusBaseResponseBodyAdviceChain());
                        break;
                    }
                    preTemp = temp;
                    temp = temp.getBaseResponseBodyAdviceChain();
                }
            }
        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        // comment explaining why the method is empty
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
            throws BeansException {
        StreamScannerConfigurer streamScannerConfigurer =
                configurableListableBeanFactory.getBean(StreamScannerConfigurer.class);
        // 设置包信息为空
        streamScannerConfigurer.setBasePackages(new HashSet<>());
        DefaultListableBeanFactory defaultListableBeanFactory =
                (DefaultListableBeanFactory) configurableListableBeanFactory;
        // 移除debug druid 级别的 log
        String slf4jBeanName = "slf4jLogFilter";
        if (defaultListableBeanFactory.containsBeanDefinition(slf4jBeanName)) {
            defaultListableBeanFactory.removeBeanDefinition(slf4jBeanName);
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
            rootBeanDefinition.setBeanClass(Slf4jLogInfoLevelFilter.class);
            defaultListableBeanFactory.registerBeanDefinition(slf4jBeanName, rootBeanDefinition);
        }
        String dynamicMapperHandlerBeanName = "dynamicMapperHandler";
        defaultListableBeanFactory.removeBeanDefinition(dynamicMapperHandlerBeanName);
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
        // 使用自定义handler
        rootBeanDefinition.setBeanClass(DynamicMapperPlusHandler.class);
        defaultListableBeanFactory.registerBeanDefinition(dynamicMapperHandlerBeanName, rootBeanDefinition);
        // 需要手动控制依赖启动，在@PostConstruct下无法找到动态Mapper，
        String[] dynamicServiceNameArray = defaultListableBeanFactory.getBeanNamesForType(DynamicServicePlusImpl.class);
        for (String dynamicServiceName : dynamicServiceNameArray) {
            defaultListableBeanFactory.getBeanDefinition(dynamicServiceName).setDependsOn(dynamicMapperHandlerBeanName);
        }
    }

    @Bean
    @Primary
    public MybatisPlusInterceptor mybatisPlusInterceptor(
            DataSourceProperties dataSourceProperties,
            @Autowired(required = false) DataAuditFieldTranslation dataAuditFieldTranslation) {
        String driverClassName = dataSourceProperties.getDriverClassName();
        DbType dbType = null;
        if (!StrUtil.isEmpty(driverClassName)) {
            if (driverClassName.indexOf("postgres") != -1) {
                dbType = DbType.POSTGRE_SQL;
            } else if (driverClassName.indexOf("mysql") != -1) {
                dbType = DbType.MYSQL;
            } else if (driverClassName.indexOf("oracle") != -1) {
                dbType = DbType.ORACLE;
            } else if (driverClassName.indexOf("mariadb") != -1) {
                dbType = DbType.MARIADB;
            } else if (driverClassName.indexOf("sqlserver") != -1) {
                dbType = DbType.SQL_SERVER;
            } else if (driverClassName.indexOf("sqlite") != -1) {
                dbType = DbType.SQLITE;
            } else if (driverClassName.indexOf("h2") != -1) {
                dbType = DbType.H2;
            }
        } else {
            dbType = getDbType();
        }
        if (dbType == null) {
            throw ExceptionUtil.wrapRuntime("暂时不支持此数据库");
        }

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 乐观锁
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 根据配置是否启用数据审计
        String enabled = this.applicationContext.getEnvironment().getProperty("zebra.database.audit.enabled", "false");
        if (Boolean.parseBoolean(enabled)) {
            DataAuditInterceptor dataAuditInterceptor = getDataAuditInterceptor(dataAuditFieldTranslation);
            interceptor.addInnerInterceptor(dataAuditInterceptor);
        }
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType));
        return interceptor;
    }

    public DataAuditInterceptor getDataAuditInterceptor(DataAuditFieldTranslation dataAuditFieldTranslation) {
        String ignoredColumns =
                this.applicationContext.getEnvironment().getProperty("zebra.database.audit.ignored-columns", "");
        DataAuditInterceptor dataAuditInterceptor = new DataAuditInterceptor();
        if (!ignoredColumns.isEmpty()) {
            Properties properties = new Properties();
            properties.setProperty("ignoredColumns", ignoredColumns);
            dataAuditInterceptor.setProperties(properties);
        }
        if (dataAuditFieldTranslation != null) {
            dataAuditInterceptor.setDataAuditFieldTranslation(dataAuditFieldTranslation);
        }
        return dataAuditInterceptor;
    }

    private DbType getDbType() {
        String primary = applicationContext.getEnvironment().getProperty("spring.datasource.dynamic." + "primary");
        if (StrUtil.isEmpty(primary)) {
            return null;
        }
        String url = applicationContext
                .getEnvironment()
                .getProperty("spring.datasource.dynamic.datasource." + primary + ".url");
        if (StrUtil.isEmpty(url)) {
            return null;
        }
        return com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url);
    }

    @Bean
    @ConditionalOnMissingBean(MetaObjectHandler.class)
    public MetaObjectHandlerPlus plusMetaObjectHandler() {
        MetaObjectHandlerPlus metaObjectHandlerPlus = new MetaObjectHandlerPlus();
        metaObjectHandlerPlus.addMetaObjectHandler(new AutoFillMetaObjectHandler());
        return metaObjectHandlerPlus;
    }
}
